/*
 * Essential-C - Essential C Libraries for Developers
 * Copyright (c) 2004-2008 Tom Bradford <tom@tbradford.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a 
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 */

#include <pthread.h>
#include <stdio.h>

#include "esc_thread.h"

/* Private Types and Variables ***********************************************/

/** Implementation type for Thread instances */
struct esc_thread_t {
    esc_mem_t* mem;
    pthread_t thread;    
    esc_thread_function_t* runnableFunction;
    esc_thread_function_t* cleanupFunction;
    void* context;
    bool running;
    bool completed;
};

struct esc_mutex_t {
    esc_mem_t* mem;
    pthread_cond_t cond;
    pthread_mutex_t mutex;
};

/* Private Functions *********************************************************/

static void private_cleanup(esc_thread_t* thread)
{
    if ( thread->cleanupFunction )
        thread->cleanupFunction(thread, thread->context);
}

/*---------------------------------------------------------------------------*/

/** Primary Thread execution wrapper */
static void* private_run(esc_thread_t* thread)
{
    pthread_cleanup_push(private_cleanup, thread);

    if ( thread->runnableFunction ) {
        thread->running = true;
        thread->runnableFunction(thread, thread->context);
        thread->running = false;        
    }
    
    pthread_cleanup_pop(1);
        
    thread->completed = true;
    
    pthread_exit(thread);
        
    return thread;
}

/*---------------------------------------------------------------------------*/

static void* private_esc_thread_finalize(esc_thread_t* thread)
{
    if ( thread->completed )
        pthread_detach(thread->thread);
        
    return thread;
}

/*---------------------------------------------------------------------------*/

static void* private_esc_mutex_finalize(esc_mutex_t* mutex)
{ 
    pthread_mutex_destroy(&(mutex->mutex));
    pthread_cond_destroy(&(mutex->cond));  
    
    return mutex;  
}

/* Public Functions **********************************************************/

esc_thread_t* esc_thread_new(esc_mem_t* mem)
{
    esc_thread_t* thread = esc_mem_malloc(mem, sizeof(esc_thread_t));
    
    thread->runnableFunction = NULL;
    thread->cleanupFunction = NULL;
    thread->context = NULL;
    thread->running = false;
    thread->completed = false;

    // Register the disposal callback
    esc_ptr_setDisposalFunction(thread, private_esc_thread_finalize); 

    return thread;    
}

/*---------------------------------------------------------------------------*/

esc_thread_t* esc_thread_init(esc_thread_t* thread,
                              esc_thread_function_t* runnableFunction,
                              esc_thread_function_t* cleanupFunction,
                              void* context)
{
    thread->runnableFunction = runnableFunction;
    thread->cleanupFunction = cleanupFunction;
    thread->context = context;
    
    return thread;
}

/*---------------------------------------------------------------------------*/

void esc_thread_setRunnableFunction(esc_thread_t* thread, 
                                    esc_thread_function_t* runnableFunction)
{
    thread->runnableFunction = runnableFunction;
}

/*---------------------------------------------------------------------------*/

esc_thread_function_t* esc_thread_getRunnableFunction(esc_thread_t* thread)
{
    return thread->runnableFunction;
}

/*---------------------------------------------------------------------------*/

void esc_thread_setCleanupFunction(esc_thread_t* thread, 
                                   esc_thread_function_t* cleanupFunction)
{
    thread->cleanupFunction = cleanupFunction;
}

/*---------------------------------------------------------------------------*/

esc_thread_function_t* esc_thread_getCleanupFunction(esc_thread_t* thread)
{
    return thread->cleanupFunction;
}

/*---------------------------------------------------------------------------*/

void esc_thread_setContext(esc_thread_t* thread, void* context)
{
    thread->context = context;
}

/*---------------------------------------------------------------------------*/

void* esc_thread_getContext(esc_thread_t* thread)
{
    return thread->context;
}

/*---------------------------------------------------------------------------*/

bool esc_thread_start(esc_thread_t* thread)
{
    if ( thread->runnableFunction && !thread->running ) {
        if ( pthread_create(&(thread->thread), NULL, private_run, thread) == 0 )
            return true;
        else
            return false;
    }
    else
        return false;
}

/*---------------------------------------------------------------------------*/

bool esc_thread_stop(esc_thread_t* thread)
{
    if ( thread->running ) 
        return pthread_cancel(thread->thread) == 0;
    else
        return false;
}

/*---------------------------------------------------------------------------*/

bool esc_thread_join(esc_thread_t* thread)
{
    if ( thread->running )
        return pthread_join(thread->thread, NULL) == 0;
    else
        return false;
}

/*---------------------------------------------------------------------------*/

bool esc_thread_isRunning(esc_thread_t* thread)
{
    return thread->running;
}

/*---------------------------------------------------------------------------*/

esc_mutex_t* esc_mutex_new(esc_mem_t* mem)
{
    esc_mutex_t* mutex = esc_mem_malloc(mem, sizeof(esc_mutex_t));
    
    pthread_cond_init(&(mutex->cond), NULL);    
    pthread_mutex_init(&(mutex->mutex), NULL);

    // Register the disposal callback
    esc_ptr_setDisposalFunction(mutex, private_esc_mutex_finalize); 

    return mutex;
}

/*---------------------------------------------------------------------------*/

void esc_mutex_wait(esc_mutex_t* mutex)
{
    pthread_cond_wait(&(mutex->cond), &(mutex->mutex));
}

/*---------------------------------------------------------------------------*/

void esc_mutex_notify(esc_mutex_t* mutex)
{
    pthread_cond_signal(&(mutex->cond));
}

/*---------------------------------------------------------------------------*/

void esc_mutex_notifyAll(esc_mutex_t* mutex)
{
    pthread_cond_broadcast(&(mutex->cond));
}

/*---------------------------------------------------------------------------*/

bool esc_mutex_lock(esc_mutex_t* mutex)
{
    return pthread_mutex_lock(&(mutex->mutex)) == 0;
}

/*---------------------------------------------------------------------------*/

bool esc_mutex_tryLock(esc_mutex_t* mutex)
{
    return pthread_mutex_trylock(&(mutex->mutex)) == 0;
}

/*---------------------------------------------------------------------------*/

bool esc_mutex_unlock(esc_mutex_t* mutex)
{
    return pthread_mutex_unlock(&(mutex->mutex)) == 0;
}
